import binascii
import json
import queue
import threading
import time
from collections import deque

from dht.conf import BOOTSTRAP_NODES, SLEEP_TIME, REFRESH_TABLE_TIME
from dht.krpc import Krpc
from dht.node import KNode
from dht.utils import random_id, entropy, decode_nodes, encode_nodes, get_logger, node_dump
from bencode import bdecode
from hashlib import sha1


class Client(Krpc):
    __slots__ = ("port", "table", "redis", "nodes", "logger", "token", "queue", "table_manager")

    def __init__(self, port, table, redis=None):

        self.table = table
        self.port = port
        self.redis = redis
        self.nodes = deque()
        self.logger = get_logger("logger_{}".format(9565))
        self.token = 'aaaa'
        self.queue = queue.Queue()
        self.table_manager = threading.Thread(target=self.table_manager_thread)
        Krpc.__init__(self, 9565)

    def table_manager_thread(self):
        while True:
            try:
                # node = self.get_table_queue()
                print(self.queue.qsize())
                self.table.append(self.get_table_queue())
            # except queue.Empty:
            #     time.sleep(0.01)
            except Exception:
                pass
        # pass

    def append_table_queue(self, item):
        self.queue.put(item)

    def get_table_queue(self):
        return self.queue.get()

    def get_sizes(self):
        t = 1
        while True:
            # print("get size %d" % t)
            if t >= 10:
                print("table size %d" % self.table.sizes)
                t = 1
            else:
                t += 1
                time.sleep(1)

    def find_node(self, address, nid=None):
        nid = self.get_neighbor(nid) if nid else self.table.nid
        tid = entropy(2)

        msg = {
            "t": tid,
            "y": "q",
            "q": "find_node",
            "a": {"id": nid, "target": random_id()}
        }
        self.redis.add_ip(address[0])
        self.send_krpc(msg, address)

    def rotate_secrets_time(self):
        pass

    def refresh_table(self):
        """
        定时执行 bootstrap()
        """

        t = 40
        while True:
            if t >= REFRESH_TABLE_TIME:
                for bucket in self.table:
                    for node in bucket:
                        # print("refresh table ip:%s " % node.ip)
                        self.find_node((node.ip, node.port), node.nid)
                t = 1
            else:
                t += 1
                time.sleep(1)

    # def start(self):
    #     self.joinDHT()
    #
    #     while True:
    #         try:
    #             (data, address) = self.sock.recvfrom(65536)
    #             msg = bdecode(data)
    #             print("recvfrom %s" % address.ip)
    #             self.types[msg["y"]](msg, address)
    #         except Exception as e:
    #             print(e)

    def on_message(self, msg, address):
        """
        负责返回信息的处理

        :param msg: 报文信息
        :param address: 报文地址
        """
        # print(msg)
        try:
            # `回复`
            # 对应于 KPRC 消息字典中的 y 关键字的值是 r，包含了一个附加的关键字 r。
            # 关键字 r 是字典类型，包含了返回的值。发送回复消息是在正确解析了请求消息的
            # 基础上完成的。
            if msg["y"] == b"r":
                # nodes 是字符串类型，包含了被请求节点的路由表中最接近目标节点
                # 的 K个最接近的节点的联系信息。
                # if msg["r"].get(b"nodes", None):
                self.find_node_handler(msg)
            # `请求`
            # 对应于 KPRC 消息字典中的 y 关键字的值是 q，它包含 2 个附加的关键字
            # q 和 a。关键字 q 是字符串类型，包含了请求的方法名字。关键字 a 一个字典
            # 类型包含了请求所附加的参数。
            # 而实际上我们只需要获取这两者中的 info hash，用于构造磁力链接进而获取种子。
            elif msg["y"] == b"q":
                print("get_peers")
                print(msg)
                # get_peers 与 torrent 文件的 info_hash 有关。这时 KPRC 协议中的
                # "q" = "get_peers"。get_peers 请求包含 2 个参数。第一个参数是 id，
                # 包含了请求节点的 ID。第二个参数是 info_hash，它代表 torrent 文件的 info_hash
                if msg["q"] == b"get_peers":
                    self.get_peers_received(msg, address)
                # announce_peer 表明请求的节点正在某个端口下载 torrent
                # 文件。announce_peer 包含 4 个参数。第一个参数是 id，包含了请求节点的 ID；
                # 第二个参数是 info_hash，包含了 torrent 文件的 info_hash；第三个参数是 port
                # 包含了整型的端口号，表明 peer 在哪个端口下载；第四个参数数是 token，
                # 这是在之前的 get_peers 请求中收到的回复中包含的。
                elif msg["q"] == b"announce_peer":
                    self.announce_peer_received(msg, address)

                elif msg["q"] == b'find_node':
                    self.find_node_received(msg, address)

                elif msg["q"] == b'ping':
                    self.ping_received(msg, address)
        except KeyError:
            pass

    def joinDHT(self):
        print("load back")
        self.table_manager.start()
        # t = 0
        with open("ip_port_nid", "r") as f:
            for line in f.readlines():
                obj = json.loads(line.strip())
                # print(binascii.a2b_hex(str.encode(obj['nid'], "utf-8")))
                # t += 1
                self.append_table_queue(KNode(binascii.a2b_hex(str.encode(obj['nid'], "utf-8")), obj['ip'], obj['port']))
                # time.sleep(0.1)
                # json.loads(line.strip(), object_hook=lambda obj: KNode(binascii.b2a_hex(str.encode(obj.nid, "utf-8")), obj.ip, obj.port))
        # print(self.table.sizes)
        # print(t)
        print("join DHT")
        for address in BOOTSTRAP_NODES:
            # print(address)
            self.find_node(address)
            # self.table.append(KNode())

    def find_node_handler(self, msg):
        # print("find node")
        try:
            nodes = decode_nodes(msg["r"]["nodes"])
            # print(nodes)
            for node in nodes:
                (nid, ip, port) = node
                if len(nid) != 20: continue
                if nid == self.table.nid: continue
                self.append_table_queue(KNode(nid, ip, port))
                # self.nodes.append(KNode(nid, ip, port))
                # self.find_node((ip, port), nid)
        except Exception as e:
            print(e)

    def back(self):
        t = 1
        while True:
            if t >= 30:
                with open('ip_port_nid', 'w') as f:
                    for buck in self.table:
                        for node in buck:
                            # print(node)
                            d = {"nid": bytes.decode(binascii.b2a_hex(node.nid), "utf-8"), "ip": node.ip,
                                 "port": node.port}
                            # print(d)
                            # hash = sha1()
                            # print(binascii.b2a_hex(node.nid))
                            f.write(json.dumps(d) + "\n")
                t = 1
            else:
                t += 1
                time.sleep(1)

    def get_neighbor(self, target):
        return target[:10] + random_id()[10:]

    def ping_received(self, msg, address):
        try:
            nid = msg["a"]["id"]
            msg = {
                "t": msg["t"],
                "y": "r",
                "r": {"id": self.get_neighbor(nid)}
            }
            self.send_krpc(msg, address)
            self.append_table_queue(KNode(nid, *address))
            # self.find_node(address, nid)
        except KeyError:
            pass

    def find_node_received(self, msg, address):
        try:
            print("find_node")
            target = msg["a"]["target"]
            neighbors = self.table.get_neighbors(target)

            nid = msg["a"]["id"]
            msg = {
                "t": msg["t"],
                "y": "r",
                "r": {
                    "id": self.get_neighbor(target),
                    "nodes": encode_nodes(neighbors)
                }
            }
            self.append_table_queue(KNode(nid, *address))
            self.send_krpc(msg, address)
            # self.find_node(address, nid)
        except KeyError:
            pass

    def get_peers_received(self, msg, address):
        try:
            info_hash = msg[b"a"][b"info_hash"]
            self.logger.info(info_hash)
            neighbors = self.table.get_neighbors(info_hash)

            nid = msg["a"]["id"]
            msg = {
                "t": msg["t"],
                "y": "r",
                "r": {
                    "id": self.get_neighbor(info_hash),
                    "nodes": encode_nodes(neighbors)
                }
            }
            print("get_peers_received")
            print(msg)
            self.append_table_queue(KNode(nid, *address))
            self.send_krpc(msg, address)
            self.redis.add_hash(info_hash)
            # self.find_node(address, nid)
        except KeyError:
            pass

    # def send_find_node_forever(self):
    #     """
    #     循环发送 find_node 请求
    #     """
    #     self.logger.info("send find node forever...")
    #     while True:
    #         try:
    #             # 弹出一个节点
    #             node = self.nodes.popleft()
    #             self.find_node((node.ip, node.port), node.nid)
    #             time.sleep(SLEEP_TIME)
    #         except IndexError:
    #             # 一旦节点队列为空，则重新加入 DHT 网络
    #             self.joinDHT()

    # def bs_timer(self):
    #     """
    #     定时执行 bootstrap()
    #     """
    #     t = 1
    #     while True:
    #         if t % PER_SEC_BS_TIMER == 0:
    #             t = 1
    #             self.joinDHT()
    #         t += 1
    #         time.sleep(1)

    def receive_response_forever(self):
        """
        循环接受 udp 数据
        """
        # self.logger.info("receive response forever ")
        # 首先加入到 DHT 网络
        self.joinDHT()
        while True:
            try:
                # 接受返回报文
                data, address = self.sock.recvfrom(65535)

                # 使用 bdecode 解码返回数据
                msg = bdecode(data)
                # print("recvfrom %s" % address[0])
                # 处理返回信息
                self.on_message(msg, address)
                # time.sleep(SLEEP_TIME)
            except Exception as e:
                self.logger.warning(e)

    def announce_peer_received(self, msg, address):
        try:
            info_hash = msg[b"a"][b"info_hash"]
            nid = msg["a"]["id"]
            self.logger.info(info_hash)
            msg = {
                "t": msg[b"t"],
                "y": "r",
                "r": {"id": self.get_neighbor(info_hash)}
            }

            self.append_table_queue(KNode(nid, *address))
            self.send_krpc(msg, address)
            self.redis.add_hash(info_hash)
            self.find_node(address, nid)
        except KeyError:
            pass

    # def node_dump(self, obj):
    #     return {
    #         "nid": binascii.b2a_hex(obj.nid),
    #         "ip": obj.ip,
    #         "port": obj.port
    #     }
    #
    # def node_load(self, d):
    #     return KNode(d['nid'], d['ip'], d['port'])
